/* eslint-disable no-prototype-builtins */
import {
  // isFiniteNumberOrStringOfNumber,
  // isNonWhiteText,
  getNumberSafely,
  getColorByHash,
} from "@/utils";

import { i18n } from "@/vue-i18n";
import type { T_RoiSingleOfApi } from "@/types/raw-data";
import type { KonvaGroupConfig, GraphsConfigItem } from "@/types/konva";

const $t = i18n.global.t;

export function getValidObjectSafely<T = Record<string, any>>(
  input?: unknown
): Partial<
  Exclude<
    T extends object ? T : Record<string, any>,
    undefined | null | number | bigint | boolean | string | symbol
  >
> {
  if (!input || typeof input !== "object") {
    return {};
  }

  return input;
}

export interface T_DataTransformer_ChineseIdNumberFormatter_Options {
  preferredSeparator?: "-" | "|" | "_" | ":" | "~" | " " | "";
  shouldHideLast4Chars?: boolean;
  replaceEachHiddenCharWithThisChar?: string;
}

export function getFormattedChineseIdNumber(
  input?: any,
  options?: T_DataTransformer_ChineseIdNumberFormatter_Options
): string {
  if (!input || typeof input !== "string") {
    return input as string;
  }

  const options_Reliable =
    getValidObjectSafely<T_DataTransformer_ChineseIdNumberFormatter_Options>(
      options
    );

  const {
    preferredSeparator,
    shouldHideLast4Chars,
    replaceEachHiddenCharWithThisChar: preferredHiddenChar,
  } = options_Reliable;

  let preferredSeparatorIsUsable: boolean;
  if (typeof preferredSeparator === "string") {
    const trimmedVersion = preferredSeparator.trim();

    if (trimmedVersion) {
      /** 只准采用有限的几种符号。 */
      preferredSeparatorIsUsable = /^[-|_:~]$/.test(trimmedVersion);
    } else {
      if (preferredSeparator.length > 1) {
        console.warn(
          "【中国居民身份证】格式化工具： preferredSeparator 是多个空白字符。"
        );
        preferredSeparatorIsUsable = false;
      } else if (preferredSeparator.length === 1) {
        // 允许使用单个空格。
        preferredSeparatorIsUsable = preferredSeparator === " ";
      } else {
        // 允许使用空字符串，即并不对内容作分隔。
        console.warn(
          "【中国居民身份证】格式化工具： preferredSeparator 是空字符串。"
        );
        preferredSeparatorIsUsable = true;
      }
    }
  } else {
    preferredSeparatorIsUsable = false;
  }

  const decidedSeparator: string = preferredSeparatorIsUsable
    ? (preferredSeparator as string)
    : getFormattedChineseIdNumber.defaultSeparator;

  let preferredHiddenCharIsUsable = true;

  if (
    typeof preferredHiddenChar !== "string" ||
    preferredHiddenChar.length !== 1
  ) {
    preferredHiddenCharIsUsable = false;
  } else if (/[^\d０１２３４５６７８９ xX]/.test(preferredHiddenChar)) {
    preferredHiddenCharIsUsable = false;
  } else if (preferredHiddenChar === decidedSeparator) {
    preferredHiddenCharIsUsable = false;
  }

  const decidedHiddenChar: string = preferredHiddenCharIsUsable
    ? (preferredHiddenChar as string)
    : getFormattedChineseIdNumber.hiddenCharDefaultSubstitute;

  if (input.length === 15) {
    return input;
  } else if (/^\d{17}[\dxX]$/.test(input)) {
    return [
      input.slice(0, 6),
      input.slice(6, 10),
      input.slice(10, 12),
      input.slice(12, 14),
      shouldHideLast4Chars ? decidedHiddenChar.repeat(4) : input.slice(14),
    ].join(decidedSeparator);
  } else {
    return input;
  }
}

getFormattedChineseIdNumber.defaultSeparator = "-";
getFormattedChineseIdNumber.hiddenCharDefaultSubstitute = "*";

export interface T_DataTransformer_ChineseMobilePhoneNumberFormatter_Options {
  preferredSeparator?: "-" | "|" | "_" | ":" | "~" | " " | "";
  shouldHideTheseDigits?: "middle-4" | "last-4" | "last-8";
  replaceEachHiddenCharWithThisChar?: string;
}

export function getFormattedChineseMobilePhoneNumber(
  input?: any,
  options?: T_DataTransformer_ChineseMobilePhoneNumberFormatter_Options
): string {
  if (!input || typeof input !== "string") {
    return input as string;
  }

  const options_Reliable =
    getValidObjectSafely<T_DataTransformer_ChineseMobilePhoneNumberFormatter_Options>(
      options
    );

  const {
    preferredSeparator,
    shouldHideTheseDigits,
    replaceEachHiddenCharWithThisChar: preferredHiddenChar,
  } = options_Reliable;

  let preferredSeparatorIsUsable: boolean;
  if (typeof preferredSeparator === "string") {
    const trimmedVersion = preferredSeparator.trim();

    if (trimmedVersion) {
      /** 只准采用有限的几种符号。 */
      preferredSeparatorIsUsable = /^[-|_:~]$/.test(trimmedVersion);
    } else {
      if (preferredSeparator.length > 1) {
        console.warn(
          "【中国手机号码】格式化工具： preferredSeparator 是多个空白字符。"
        );
        preferredSeparatorIsUsable = false;
      } else if (preferredSeparator.length === 1) {
        // 允许使用单个空格。
        preferredSeparatorIsUsable = preferredSeparator === " ";
      } else {
        // 允许使用空字符串，即并不对内容作分隔。
        console.warn(
          "【中国手机号码】格式化工具： preferredSeparator 是空字符串。"
        );
        preferredSeparatorIsUsable = true;
      }
    }
  } else {
    preferredSeparatorIsUsable = false;
  }

  const decidedSeparator: string = preferredSeparatorIsUsable
    ? (preferredSeparator as string)
    : getFormattedChineseMobilePhoneNumber.defaultSeparator;

  let preferredHiddenCharIsUsable = true;

  if (
    typeof preferredHiddenChar !== "string" ||
    preferredHiddenChar.length !== 1
  ) {
    preferredHiddenCharIsUsable = false;
  } else if (/[^\d０１２３４５６７８９ ]/.test(preferredHiddenChar)) {
    preferredHiddenCharIsUsable = false;
  } else if (preferredHiddenChar === decidedSeparator) {
    preferredHiddenCharIsUsable = false;
  }

  const decidedHiddenChar: string = preferredHiddenCharIsUsable
    ? (preferredHiddenChar as string)
    : getFormattedChineseMobilePhoneNumber.hiddenCharDefaultSubstitute;

  if (/^1\d{10}$/.test(input)) {
    return [
      input.slice(0, 3),
      shouldHideTheseDigits === "last-8" || shouldHideTheseDigits === "middle-4"
        ? decidedHiddenChar.repeat(4)
        : input.slice(3, 7),
      shouldHideTheseDigits === "last-8" || shouldHideTheseDigits === "last-4"
        ? decidedHiddenChar.repeat(4)
        : input.slice(7),
    ].join(decidedSeparator);
  } else {
    return input;
  }
}

getFormattedChineseMobilePhoneNumber.defaultSeparator = "-";
getFormattedChineseMobilePhoneNumber.hiddenCharDefaultSubstitute = "×";

export interface T_DataTransformer_NumberFormatter_Options {
  digitsGroupingWidth?: number;
  preferredSeparator?: "" | "'" | ",";
  shouldNotWriteNaNAsEmmptyText?: boolean;
}

export function getFormattedNumber(
  input?: any,
  options?: T_DataTransformer_NumberFormatter_Options
): string {
  if (
    ["undefined", "function", "symbol", "boolean", "object"].includes(
      typeof input
    )
  ) {
    return "";
  }

  if (typeof input === "string") {
    const timmed = input.trim();

    if (!timmed) {
      return "";
    }

    if (/^\+?Infinity$/i.test(timmed)) {
      return "正无穷";
    } else if (/^-Infinity$/i.test(timmed)) {
      return "负无穷";
    }
  }

  const options_Reliable =
    getValidObjectSafely<T_DataTransformer_NumberFormatter_Options>(options);

  const { preferredSeparator, shouldNotWriteNaNAsEmmptyText } =
    options_Reliable;

  let digitsGroupingWidth: number = getNumberSafely(
    options_Reliable.digitsGroupingWidth
  );

  if (!(digitsGroupingWidth > 0 && digitsGroupingWidth < 6)) {
    digitsGroupingWidth = getFormattedNumber.digitsGroupingDefaultWidth;
  }

  const n = +input;

  if (isNaN(n)) {
    return shouldNotWriteNaNAsEmmptyText ? "无效数字" : "";
  }
  if (!isFinite(n)) {
    return n > 0 ? "正无穷" : "负无穷";
  }

  const originalText = `${n}`.trim();

  /** 转换成数字再转换回成文本，仍是科学计数法。 */
  if (/[+-]?\d*\.?\d+[eE][+-]?\d+/.test(originalText)) {
    return originalText;
  }

  let preferredSeparatorIsUsable: boolean;
  if (typeof preferredSeparator === "string") {
    const trimmedVersion = preferredSeparator.trim();

    if (trimmedVersion) {
      /** 只准采用有限的几种符号。 */
      preferredSeparatorIsUsable = /^[,']$/.test(trimmedVersion);
    } else {
      if (preferredSeparator.length > 1) {
        console.warn(
          "【数字】文本格式化工具： preferredSeparator 是多个空白字符。"
        );
        preferredSeparatorIsUsable = false;
      } else if (preferredSeparator.length === 1) {
        // 不允许使用单个空格。
        preferredSeparatorIsUsable = false;
      } else {
        // 允许使用空字符串，即并不对内容作分隔。
        console.warn(
          "【数字】文本格式化工具： preferredSeparator 是空字符串。"
        );
        preferredSeparatorIsUsable = true;
      }
    }
  } else {
    preferredSeparatorIsUsable = false;
  }

  const decidedSeparator: string = preferredSeparatorIsUsable
    ? (preferredSeparator as string)
    : getFormattedNumber.defaultSeparator;

  let sign = "";

  let [intPartText, fracPartText] = originalText.split(".");
  if (fracPartText) {
    fracPartText = `.${fracPartText}`;
  } else {
    fracPartText = "";
  }

  if (/^[+-]/.test(intPartText)) {
    sign = intPartText[0];
    intPartText = intPartText.slice(1);
  }

  let intResult = intPartText.slice(-digitsGroupingWidth);
  intPartText = intPartText.slice(0, -digitsGroupingWidth);

  while (intPartText.length > 0) {
    intResult = `${intPartText.slice(-digitsGroupingWidth)}${decidedSeparator}${intResult}`;
    intPartText = intPartText.slice(0, -digitsGroupingWidth);
  }

  return `${sign}${intResult}${fracPartText}`;
}

getFormattedNumber.digitsGroupingDefaultWidth = 4;
getFormattedNumber.defaultSeparator = "'";

export function getTimeComponentStrings(moment?: any): T_TimeStrings {
  const defaultResult = {
    year: "",
    month: "",
    day: "",
    hour: "",
    minute: "",
    second: "",
    weekday: "",
  };

  const weekdays = [
    $t("星期日"),
    $t("星期一"),
    $t("星期二"),
    $t("星期三"),
    $t("星期四"),
    $t("星期五"),
    $t("星期六"),
  ];

  const d = new Date(moment);
  if (!d || typeof d.getFullYear !== "function") {
    return defaultResult;
  }

  const yeaI = d.getFullYear();
  const monI = d.getMonth() + 1;
  const dayI = d.getDate();
  const houI = d.getHours();
  const minI = d.getMinutes();
  const secI = d.getSeconds();
  const currentDayIndex = d.getDay();

  if (isNaN(yeaI)) {
    return defaultResult;
  }

  const yea = `${yeaI}`;
  const mon = monI > 9 ? `${monI}` : `0${monI}`;
  const day = dayI > 9 ? `${dayI}` : `0${dayI}`;
  const hou = houI > 9 ? `${houI}` : `0${houI}`;
  const min = minI > 9 ? `${minI}` : `0${minI}`;
  const sec = secI > 9 ? `${secI}` : `0${secI}`;
  const weekday = weekdays[currentDayIndex];

  return {
    year: yea,
    month: mon,
    day,
    hour: hou,
    minute: min,
    second: sec,
    weekday,
  };
}

export interface T_DataTransformer_DateFormatter_Options {
  preferredSeparator?: "-" | "_" | "." | "";
}

export function getFormattedDateString(
  moment?: any,
  options?: T_DataTransformer_DateFormatter_Options
): string {
  const { preferredSeparator } =
    getValidObjectSafely<T_DataTransformer_DateFormatter_Options>(options);

  let decidedSeparator: string;

  if (
    typeof preferredSeparator === "string" &&
    ["-", "_", ".", ""].includes(preferredSeparator)
  ) {
    decidedSeparator = preferredSeparator;
  } else {
    decidedSeparator = "-";
  }

  const { year, month, day } = getTimeComponentStrings(moment);

  return `${year}${decidedSeparator}${month}${decidedSeparator}${day}`;
}

export interface T_DataTransformer_TimeFormatter_Options {
  preferredSeparator?: "-" | "_" | ":" | "";
  shouldOmitSeconds?: boolean;
}

export function getFormattedTimeString(
  moment?: any,
  options?: T_DataTransformer_TimeFormatter_Options
): string {
  const { preferredSeparator, shouldOmitSeconds } =
    getValidObjectSafely<T_DataTransformer_TimeFormatter_Options>(options);

  let decidedSeparator: string;

  if (
    typeof preferredSeparator === "string" &&
    ["-", "_", ":", ""].includes(preferredSeparator)
  ) {
    decidedSeparator = preferredSeparator;
  } else {
    decidedSeparator = ":";
  }

  const { hour, minute, second } = getTimeComponentStrings(moment);

  if (shouldOmitSeconds) {
    return `${hour}${decidedSeparator}${minute}`;
  }

  return `${hour}${decidedSeparator}${minute}${decidedSeparator}${second}`;
}

export interface T_DataTransformer_DateAndTimeFormatter_Options {
  shouldEnsureOutputTextSafelyUsedInFileName?: boolean;
  shouldOmitSeconds?: boolean;
}

export function getFormattedDateAndTimeString(
  moment?: any,
  options?: T_DataTransformer_DateAndTimeFormatter_Options
): string {
  const {
    shouldEnsureOutputTextSafelyUsedInFileName: fileNameSafe,
    shouldOmitSeconds,
  } =
    getValidObjectSafely<T_DataTransformer_DateAndTimeFormatter_Options>(
      options
    );

  const dateString = getFormattedDateString(moment, {
    preferredSeparator: fileNameSafe ? "" : undefined,
  });
  const timeString = getFormattedTimeString(moment, {
    preferredSeparator: fileNameSafe ? "" : undefined,
    shouldOmitSeconds,
  });

  const dateTimeConnector: string = fileNameSafe ? "_" : " ";

  return `${dateString}${dateTimeConnector}${timeString}`;
}

export const IntlDateTimeFormatterChinese = (function (): Intl.DateTimeFormat {
  const twoDigit = "2-digit";

  const theFormatter = new Intl.DateTimeFormat("zh-CN", {
    year: "numeric",
    month: twoDigit,
    day: twoDigit,
    hour: twoDigit,
    minute: twoDigit,
    second: twoDigit,
  });

  return theFormatter;
})();

export function getFormattedDateAndTimeString2(
  moment: Date | number | string
): string {
  return IntlDateTimeFormatterChinese.format(new Date(moment));
}

export function getTimeIntegerValueOfZeroOClockOfDay(moment?: any): number {
  const d = new Date(moment);
  if (!d || typeof d.getFullYear !== "function") {
    return moment as number;
  }

  const timeValue = d.getTime();

  const hour = d.getHours();
  const minute = d.getMinutes();
  const second = d.getSeconds();
  const milsec = d.getMilliseconds();

  if (isNaN(timeValue)) {
    return moment as number;
  }

  const offsetTimeValue = ((hour * 60 + minute) * 60 + second) * 1000 + milsec;
  const zeroOClockTimeValue = timeValue - offsetTimeValue;

  return zeroOClockTimeValue;
}

export function getFormattedLongText(input?: any, separator = " - "): string {
  //  待做=吴乐川  该函数的代码是老旧的，可能有不足之处。
  if (typeof input !== "string" && typeof input !== "number") {
    return input;
  }

  input = `${input}`.trim();

  const resultChars = [];
  for (let i = 0; i < input.length; i++) {
    if (i % 4 === 0 && i > 0 && i <= input.length - 1) {
      resultChars.push(separator);
    }
    resultChars.push(input[i]);
  }

  return resultChars.join("");
}

export function gatherValidPropertiesOfObject<
  T extends Record<string | number, any> = Record<string | number, any>,
  T_ShouldReturnOriginalNonObject extends boolean | undefined = false,
>(
  sourceValue?: any,

  options?: {
    shouldReturnOriginalNonObject?: T_ShouldReturnOriginalNonObject;
    shouldRemoveEmptyStrings?: boolean;
    shouldRemoveWhiteStrings?: boolean;
    shouldKeepNulls?: boolean;
  }
): T_ShouldReturnOriginalNonObject extends true
  ? typeof sourceValue
  : {
      [key in keyof T]?: T[key];
    } {
  type _T_ResultObject = T_ShouldReturnOriginalNonObject extends true
    ? typeof sourceValue
    : {
        [key in keyof T]?: T[key];
      };

  const {
    shouldReturnOriginalNonObject,
    shouldRemoveEmptyStrings,
    shouldRemoveWhiteStrings,
    shouldKeepNulls,
  } = options || {};

  if (
    !sourceValue ||
    typeof sourceValue !== "object" ||
    Array.isArray(sourceValue)
  ) {
    if (shouldReturnOriginalNonObject) {
      return sourceValue;
    } else {
      return {} as _T_ResultObject;
    }
  }

  const targetObject: _T_ResultObject = {} as _T_ResultObject;

  Object.keys(sourceValue).forEach((key) => {
    const propertyName = key as keyof T;

    const value = sourceValue[propertyName];

    if (value === 0 || value === false) {
      targetObject[propertyName] = value;
    } else if (value === null) {
      if (shouldKeepNulls) {
        targetObject[propertyName] = value;
      }
    } else if (typeof value === "string") {
      const trimmed = value.trim();
      let shouldKeepThisStringValue = true;

      if (!value) {
        shouldKeepThisStringValue =
          !shouldRemoveEmptyStrings && !shouldRemoveWhiteStrings;
      } else if (!trimmed) {
        shouldKeepThisStringValue = !shouldRemoveWhiteStrings;
      }

      if (shouldKeepThisStringValue) {
        targetObject[propertyName] = value;
      }
    } else {
      targetObject[propertyName] = value;
    }
  });

  return targetObject;
}

export function ipToInt(ip: string) {
  return ip
    .split(".")
    .reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0);
}

export function intToIP(num: number) {
  return [
    (num >>> 24) & 255,
    (num >>> 16) & 255,
    (num >>> 8) & 255,
    num & 255,
  ].join(".");
}

// import { regexp_RtspUrl } from './data-shape-assertions'

// export function extractIPAddressesFromRTSP(rtspAddress: string) {
//     const match = regexp_RtspUrl.exec(rtspAddress)
//     // console.log('ip-match', match)
//     if (match) {
//         return match[5]
//     } else {
//         return null
//     }
// }

// export function extractIPPortFromRTSP(rtspAddress: string) {
//     const match = regexp_RtspUrl.exec(rtspAddress)
//     if (match) {
//         return match[10]
//     } else {
//         return null
//     }
// }

export function replaceIPAddresses(inputString: string, replacement: string) {
  const ipRegex = /\b(?:\d{1,3}\.){3}\d{1,3}\b/; ///\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g

  const resultString = inputString.replace(ipRegex, replacement);
  return resultString;
}

export const NUMBER_LEVELS_ENGLISH = [
  "",
  "k",
  "M",
  "G",
  "T",
  "P",
  "E",
  "Z",
] as const;
type _T_NumberLevelSize_Raw = 1024 | 1000;
type _T_NumberLevelSize_InOptions = 1024 | 1000 | "1024" | "1000";
export type T_NumberLevels_English = (typeof NUMBER_LEVELS_ENGLISH)[number];

export const NUMBER_LEVELS_MULITPLERS_1000: number[] = [
  /* 1 */ 1,
  /* k */ 1000,
  /* M */ 1000000,
  /* G */ 1000000000,
  /* T */ 1000000000000,
  /* P */ 1000000000000000,
  /* E */ 1000000000000000 * 1000,
  /* Z */ 1000000000000000 * 1000000,
];

export const NUMBER_LEVELS_ENGLISH_MAP_TO_MULTIPLIER_1000: {
  [key in T_NumberLevels_English]: number;
} = {
  "": 1,
  k: 1000,
  M: 1000000,
  G: 1000000000,
  T: 1000000000000,
  P: 1000000000000000,
  E: 1000000000000000 * 1000,
  Z: 1000000000000000 * 1000000,
};

export const NUMBER_LEVELS_MULITPLERS_1024: number[] = [
  /* 1 */ 1,
  /* k */ 1024,
  /* M */ 1048576,
  /* G */ 1024 * 1048576,
  /* T */ 1048576 * 1048576,
  /* P */ 1024 * 1048576 * 1048576,
  /* E */ 1048576 * 1048576 * 1048576,
  /* Z */ 1024 * 1048576 * 1048576 * 1048576,
];

export const NUMBER_LEVELS_ENGLISH_MAP_TO_MULTIPLIER_1024 = {
  "": 1,
  k: 1024,
  M: 1048576,
  G: 1024 * 1048576,
  T: 1048576 * 1048576,
  P: 1024 * 1048576 * 1048576,
  E: 1048576 * 1048576 * 1048576,
  Z: 1024 * 1048576 * 1048576 * 1048576,
};

function _numberLevels_DecideSizeLevel(
  levelSize?: _T_NumberLevelSize_InOptions
): _T_NumberLevelSize_Raw {
  let usedLevelSize: _T_NumberLevelSize_Raw;
  if (typeof levelSize === "string") {
    usedLevelSize = +levelSize as _T_NumberLevelSize_Raw;
  } else if (typeof levelSize === "number") {
    usedLevelSize = levelSize;
  } else {
    usedLevelSize = 1024;
  }

  if (usedLevelSize !== 1000) {
    usedLevelSize = 1024;
  }

  return usedLevelSize;
}

function _numberLevels_ParseLevelText(
  input?: T_NumberLevels_English
): T_NumberLevels_English {
  const decidedText = NUMBER_LEVELS_ENGLISH.includes(
    input as T_NumberLevels_English
  )
    ? (input as T_NumberLevels_English)
    : "";
  return decidedText;
}

function _numberLevels_GetMultiplierViaLevelText(
  levelText?: T_NumberLevels_English,
  levelSize?: _T_NumberLevelSize_InOptions,
  shouldDebug?: boolean
): {
  usedLevelText: T_NumberLevels_English;
  usedLevelSize: _T_NumberLevelSize_Raw;
  pickedMuliplier: number;
} {
  const usedLevelSize = _numberLevels_DecideSizeLevel(levelSize);
  const usedLevelText = _numberLevels_ParseLevelText(levelText);

  const pickedMuliplier =
    usedLevelSize === 1024
      ? NUMBER_LEVELS_ENGLISH_MAP_TO_MULTIPLIER_1024[usedLevelText]
      : NUMBER_LEVELS_ENGLISH_MAP_TO_MULTIPLIER_1000[usedLevelText];

  shouldDebug &&
    console.debug(
      "levelText",
      levelText,
      "usedLevelText",
      usedLevelText,
      pickedMuliplier
    );

  return {
    usedLevelSize,
    usedLevelText,
    pickedMuliplier,
  };
}

export function getNumbersOfSameLevel(
  rawNumbers?: number | string | Array<number | string>,
  options?: {
    levelSize?: _T_NumberLevelSize_InOptions;
    desiredLevel?: T_NumberLevels_English;
    knownCommonBaseLevelText?: T_NumberLevels_English;
    shouldDebug?: boolean;
  }
): {
  rawValues: Array<any>;
  almostRawNumbers: Array<number | undefined>;
  numbersAtTheLevel: number[];
  numberShortTexts: string[];
  fullTexts: string[];
  usedLevelSize: _T_NumberLevelSize_Raw;
  pickedLevelText: T_NumberLevels_English;
  pickedLevelIndex: number;
  pickedMuliplier: number;
} {
  const { shouldDebug, levelSize, desiredLevel, knownCommonBaseLevelText } =
    options || {};

  const {
    usedLevelSize,
    // usedLevelText: initCommonLevelText,
    pickedMuliplier: initCommonMultipler,
  } = _numberLevels_GetMultiplierViaLevelText(
    knownCommonBaseLevelText,
    levelSize,
    shouldDebug
  );

  let _desiredLevelText: T_NumberLevels_English | undefined = undefined;
  let _desiredLevelTextIndex = -1;

  if (typeof desiredLevel === "string") {
    _desiredLevelTextIndex = NUMBER_LEVELS_ENGLISH.indexOf(desiredLevel);
    if (_desiredLevelTextIndex >= 0) {
      _desiredLevelText = NUMBER_LEVELS_ENGLISH[_desiredLevelTextIndex];
    }
  }

  function _processSingleRawNumber(
    rawNumber?: number | string
  ): number | undefined {
    let _number: number | undefined = undefined;

    if (typeof rawNumber === "number") {
      if (isFinite(rawNumber)) {
        _number = rawNumber;
      }
    } else if (typeof rawNumber === "string") {
      const trimmed = rawNumber.trim();
      if (trimmed) {
        const tempNumber = +trimmed;
        if (isFinite(tempNumber)) {
          _number = tempNumber;
        }
      }
    }

    return _number;
  }

  let rawValues: Array<any> = [];
  let almostRawNumbers: Array<number | undefined> = [];

  if (!Array.isArray(rawNumbers)) {
    rawValues = [rawNumbers];
    const singleNumber = _processSingleRawNumber(rawNumbers);
    if (singleNumber !== undefined) {
      almostRawNumbers = [singleNumber * initCommonMultipler];
    }
  } else {
    rawValues = rawNumbers;
    almostRawNumbers = rawNumbers.map((someRawNumber) => {
      const n = _processSingleRawNumber(someRawNumber);
      if (n === undefined) {
        return n;
      }
      return n * initCommonMultipler;
    });
  }

  shouldDebug &&
    console.log(
      "initCommonMultipler",
      initCommonMultipler,
      "almostRawNumbers",
      almostRawNumbers
    );

  let resultNumbers: number[] = [];
  let pickedLevelIndex: number;
  let pickedLevelText: T_NumberLevels_English;

  if (_desiredLevelText === undefined) {
    /**
     * 该分支（即指 if 分支），代表入口参数未指明数量级。故须处理 1 到 2 遍，以求得统一的数量级。
     */

    // 如果没有给出任何合规的数字，则可省去一些步骤。
    if (!almostRawNumbers.length) {
      pickedLevelIndex = 0;
    } else {
      // 先处理第 1 遍。各个原始数字的数量级可能各不相同。目标是找到所有原始数字中最高的数量级。
      // const tempNumberLevelIndices: number[] = []
      const resultLevelIndicesDict: Record<string, number> = {};

      resultNumbers = almostRawNumbers.map(
        (someRawNumber /* , arrayIndex */) => {
          if (someRawNumber === undefined) {
            // tempNumberLevelIndices[arrayIndex] = -1
            return 0;
          }

          let levelIndexOfThisNumber = 0;
          let resultNumber = someRawNumber;
          while (resultNumber > usedLevelSize) {
            resultNumber = resultNumber / usedLevelSize;
            levelIndexOfThisNumber++;
          }

          if (!resultLevelIndicesDict[levelIndexOfThisNumber]) {
            resultLevelIndicesDict[levelIndexOfThisNumber] = 1;
          } else {
            resultLevelIndicesDict[levelIndexOfThisNumber]++;
          }

          // tempNumberLevelIndices[arrayIndex] = levelIndexOfThisNumber
          return resultNumber;
        }
      );

      const validTempLevels = Object.keys(resultLevelIndicesDict).map(
        (key) => +key
      );
      pickedLevelIndex = Math.max(...validTempLevels);
      pickedLevelText = NUMBER_LEVELS_ENGLISH[pickedLevelIndex];
    }

    pickedLevelText = NUMBER_LEVELS_ENGLISH[pickedLevelIndex];

    // 所谓“第 2 遍”，其处理与“入口参数已指明数量级”的做法完全相同。故安排在之后处理。
  } else {
    /**
     * 该分支（即指 else 分支），代表“入口参数已指明数量级”。
     */

    pickedLevelIndex = _desiredLevelTextIndex;
    pickedLevelText = _desiredLevelText;
  }

  resultNumbers = almostRawNumbers.map((someRawNumber) => {
    if (someRawNumber === undefined) {
      return 0;
    }

    let levelIndexOfThisNumber = 0;
    let resultNumber = someRawNumber;
    while (levelIndexOfThisNumber < pickedLevelIndex) {
      resultNumber = resultNumber / usedLevelSize;
      levelIndexOfThisNumber++;
    }

    return resultNumber;
  });

  const numberShortTexts: string[] = resultNumbers.map(
    (someNumber) => `${+someNumber.toFixed(2)}`
  );
  const fullTexts: string[] = numberShortTexts.map(
    (someNumberText) => `${someNumberText}${pickedLevelText}`
  );
  const pickedMuliplier =
    usedLevelSize === 1024
      ? NUMBER_LEVELS_MULITPLERS_1024[pickedLevelIndex]
      : NUMBER_LEVELS_MULITPLERS_1000[pickedLevelIndex];

  const result = {
    rawValues,
    almostRawNumbers,
    numbersAtTheLevel: resultNumbers,
    numberShortTexts,
    fullTexts,
    usedLevelSize,
    pickedLevelIndex,
    pickedLevelText,
    pickedMuliplier,
  };

  shouldDebug &&
    console.debug("getNumbersOfSameLevel", rawNumbers, options, result);

  return result;
}

export function getConfigOfSinglFraction(options: {
  part: any;
  total: any;
  normalizedUnit: string;
  levelSize?: _T_NumberLevelSize_Raw;
  knownCommonBaseLevelText?: T_NumberLevels_English;
  shouldDebug?: boolean;
}): {
  totalValueIsOk: boolean;
  fractionValueIsOk: boolean;

  part_Raw: any;
  part_Number: number;
  part_NumberNormalized: number;
  part_Text: string;

  total_Raw: any;
  total_Number: number;
  total_NumberNormalized: number;
  total_Text: string;

  fraction_Number: number;
  fraction_ValueText: string;
  fraction_PercentageNumber: number;
  fraction_PercentageText: string;
  fraction_FullText: string;

  commonUnitText: string;
  commonNumberLevelIndex: number;
  knownCommonBaseLevelText: T_NumberLevels_English;
  commonNumberLevelText: T_NumberLevels_English;
} {
  type _T_Results = ReturnType<typeof getConfigOfSinglFraction>;

  const {
    levelSize,
    part,
    total,
    normalizedUnit,
    knownCommonBaseLevelText,
    shouldDebug,
  } = options || {};

  const {
    usedLevelText: initLevelText,
    usedLevelSize,
    pickedMuliplier: initMuliplier,
  } = _numberLevels_GetMultiplierViaLevelText(
    knownCommonBaseLevelText,
    levelSize,
    shouldDebug
  );

  const part_NumberNormalized = getNumberSafely(part) * initMuliplier;
  const total_NumberNormalized = getNumberSafely(total) * initMuliplier;

  const results1 = getNumbersOfSameLevel(
    [
      part, // part_NumberNormalized,
      total, // total_NumberNormalized,
    ],
    {
      levelSize: usedLevelSize,
      knownCommonBaseLevelText: initLevelText,
      desiredLevel: undefined,
      shouldDebug,
    }
  );

  const {
    fullTexts,
    numbersAtTheLevel,
    pickedLevelText: commonNumberLevelText,
    pickedLevelIndex: commonNumberLevelIndex,
  } = results1;

  const [part_Number, total_Number] = numbersAtTheLevel;

  const partValueIsOk = isFinite(part_NumberNormalized);
  const totalValueIsOk = isFinite(total_NumberNormalized);
  const fractionValueIsOk = totalValueIsOk && partValueIsOk; // && part_Number <= total_Number

  let part_Text: string;
  let total_Text: string;
  let fraction_Number: number;
  let fraction_ValueText: string;
  let fraction_FullText: string;
  let commonUnitText: string;
  let fraction_PercentageNumber: number;
  let fraction_PercentageText: string;

  if (partValueIsOk) {
    part_Text = fullTexts[0];
  } else {
    part_Text = "-";
  }

  if (totalValueIsOk) {
    total_Text = fullTexts[1];
  } else {
    total_Text = "-";
  }

  if (fractionValueIsOk) {
    fraction_Number = part_Number / total_Number;
    commonUnitText = `${commonNumberLevelText}${normalizedUnit}`;
    fraction_ValueText = `${+part_Number.toFixed(2)} / ${+total_Number.toFixed(2)}`;
    fraction_FullText = `${fraction_ValueText}${commonUnitText}`;
    fraction_PercentageNumber = fraction_Number * 100;
    fraction_PercentageText = `${+fraction_PercentageNumber.toFixed(2)}%`;
  } else {
    fraction_Number = NaN;
    fraction_PercentageNumber = NaN;
    commonUnitText = normalizedUnit;
    fraction_ValueText = "- / -";
    fraction_FullText = `- / -${commonUnitText}`;
    fraction_PercentageText = "-%";
  }

  const results: _T_Results = {
    totalValueIsOk,
    fractionValueIsOk,

    part_Raw: part,
    part_Number,
    part_NumberNormalized,
    part_Text,

    total_Raw: total,
    total_Number,
    total_NumberNormalized,
    total_Text,

    fraction_Number,
    fraction_ValueText,
    fraction_FullText,
    fraction_PercentageText,
    fraction_PercentageNumber,

    commonUnitText,
    commonNumberLevelIndex,
    commonNumberLevelText,
    knownCommonBaseLevelText: knownCommonBaseLevelText || "",
  };

  shouldDebug && console.debug("getConfigOfSinglFraction", options, results);

  return results;
}

export function recursiveObjectValues(obj: Record<string, any>) {
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      if (typeof obj[key] === "object") {
        // 如果属性值是一个对象，递归遍历它
        recursiveObjectValues(obj[key]);
        if (!Object.keys(obj[key]).length) {
          delete obj[key];
        }
      } else {
        // 如果属性值不是对象并且值为空，删除它
        if (!obj[key] && obj[key] !== false) delete obj[key];
      }
    }
  }
}

//js随机产生颜色
export function randomColor(colorsCount?: number): string {
  return getColor(Math.random() * (colorsCount || 6), colorsCount);
}
export function getColor(
  colorIndex: number,
  colorsCount?: number,
  opacity?: number
): string {
  if (!colorsCount) {
    colorsCount = 6;
  }
  const hue: number = (360 / colorsCount) * Math.floor(colorIndex);
  const color = `hsla(${hue}, 100%, 63%, ${opacity || 100}%)`;
  return color;
}

export function isValidObject<T = Record<string, any>>(
  input?: unknown,
  options?: {
    allowToBeArray?: boolean;
    allowToBeFunction?: boolean;
  }
): input is Partial<
  Exclude<
    T extends object ? Record<string, any> : T,
    undefined | null | number | bigint | boolean | string | symbol
  >
> {
  if (!input) {
    return false;
  }

  if (!options) {
    options = {};
  }

  if (typeof input === "object") {
    return !Array.isArray(input) || !!options.allowToBeArray;
  }

  if (typeof input === "function") {
    return !!options.allowToBeFunction;
  }

  return false;
}

/**
 * 该工具函数多用于前端收到完整数据表后做所谓“假分页”。
 */
export function sliceListDataAccordingToPaginationConfig<T_Entry = any>(
  listData?: Array<T_Entry>,
  paginationConfig?: T_PaginationConfigFull,
  options?: {
    pageIndexStartsFromZero?: boolean;
  }
): Array<T_Entry> {
  if (!Array.isArray(listData)) {
    return [];
  }

  if (!isValidObject<T_PaginationConfigFull>(paginationConfig)) {
    return listData;
  }

  const { pageIndexStartsFromZero } = options || {};

  const listDataCount = listData.length;

  const { currentPageIndex, entriesCountPerPage, entriesTotalCount } =
    paginationConfig;

  const pageIndexOffset = pageIndexStartsFromZero ? 1 : 0;
  const _currentVirtualPageIndex = currentPageIndex + pageIndexOffset;

  if (entriesTotalCount !== listDataCount) {
    console.warn(
      "sliceListDataAccordingToPaginationConfig： 由 listData 中的数据笔数与「分页配置」中给出的「entriesTotalCount」相左。",
      "分页配置中给出的 entriesTotalCount = ",
      entriesTotalCount,
      "实际的数据笔数 = ",
      listDataCount
    );
  }

  const sliceStart = entriesCountPerPage * (_currentVirtualPageIndex - 1);
  const sliceEnd = Math.min(
    listDataCount,
    entriesCountPerPage * _currentVirtualPageIndex
  );

  if (!(sliceStart < sliceEnd)) {
    return listData;
  }

  return listData.slice(sliceStart, sliceEnd);
}

/**
 * 根据最新尺寸更新 Konva 配置
 *
 * @export
 * @param {KonvaGroupConfig[]} konvaList
 * @param {number} currWidth
 * @param {number} currHeight
 */
export function updateKonvaConfigBasedOnLatestSize(
  konvaList: KonvaGroupConfig[],
  currWidth: number,
  currHeight: number
) {
  konvaList.forEach((item: GraphsConfigItem) => {
    const canvaSqrt = Math.round(Math.sqrt(currWidth ** 2 + currHeight ** 2));
    switch (item.konvaGraphConfig?.graphType) {
      case "pose":
      case "hand":
        item.graphsConfigItems?.forEach((child: GraphsConfigItem) => {
          if (child.vueComponentName === "v-circle") {
            child.konvaGraphConfig.x *= currWidth;
            child.konvaGraphConfig.y *= currHeight;
            child.konvaGraphConfig.radius = Math.max(canvaSqrt * 0.004, 2);
          }
          if (child.vueComponentName === "v-line") {
            child.konvaGraphConfig.points = [
              child.konvaGraphConfig?.points?.[0] * currWidth,
              child.konvaGraphConfig?.points?.[1] * currHeight,
              child.konvaGraphConfig?.points?.[2] * currWidth,
              child.konvaGraphConfig?.points?.[3] * currHeight,
            ];
            child.konvaGraphConfig.strokeWidth = Math.max(canvaSqrt * 0.003, 1);
          }
        });
        break;
      case "action":
        item.konvaGraphConfig.x *= currWidth;
        item.konvaGraphConfig.y *= currHeight;

        item.graphsConfigItems?.forEach((child: GraphsConfigItem) => {
          if (child.vueComponentName === "v-label") {
            child.graphsConfigItems?.forEach((child2: GraphsConfigItem) => {
              if (child2.vueComponentName === "v-text") {
                child2.konvaGraphConfig.fontSize = 10 + canvaSqrt * 0.0025;
              }
            });
          }
        });
        break;
      case "target": {
        item.konvaGraphConfig.x *= currWidth;
        item.konvaGraphConfig.y *= currHeight;
        item.konvaGraphConfig.width *= currWidth;
        item.konvaGraphConfig.height *= currHeight;

        const { y, width, height } = item.konvaGraphConfig;
        const scale = 1 + canvaSqrt * 0.002;
        const pointerDirection = y < 40 ? "up" : "down";

        item.graphsConfigItems?.forEach((child: GraphsConfigItem) => {
          if (child.vueComponentName === "v-rect") {
            child.konvaGraphConfig.width = width;
            child.konvaGraphConfig.height = height;
            child.konvaGraphConfig.strokeWidth = scale;
          }
          if (child.vueComponentName === "v-label") {
            child.konvaGraphConfig.x = width / 2;
            child.konvaGraphConfig.y = 0;
            child.graphsConfigItems?.forEach((child2: GraphsConfigItem) => {
              if (child2.vueComponentName === "v-tag") {
                child2.konvaGraphConfig.pointerWidth = scale * 1.25;
                child2.konvaGraphConfig.pointerHeight = scale * 2.5;
                child2.konvaGraphConfig.pointerDirection = pointerDirection;
              }
              if (child2.vueComponentName === "v-text") {
                child2.konvaGraphConfig.fontSize = 10 + canvaSqrt * 0.0025;
              }
            });
          }
        });
        break;
      }
      case "roi":
        item.graphsConfigItems?.forEach((child: GraphsConfigItem) => {
          const newPoints = (child.konvaGraphConfig as any).rawPoints?.map(
            (point: number, index: number) => {
              return index % 2 === 0
                ? Math.ceil(point * currWidth)
                : Math.ceil(point * currHeight);
            }
          );
          child.konvaGraphConfig.points = newPoints;
        });
        break;
      default:
        break;
    }
  });

  return konvaList;
}
/**
 * 目标检测结果处理程序
 *
 * @param {MmC3d_InferenceResult_Item} res
 * @return {*}  {KonvaGroupConfig}
 */
const targetResultHandler = (
  res: MmC3d_InferenceResult_Item,
  unConvertConfidence?: boolean
): KonvaGroupConfig => {
  const {
    class_name,
    confidence,
    bbox: { left, top, width, height },
  } = res;

  const confidencePercent = Math.round((confidence ?? 0) * 100);
  const confidenceTip = unConvertConfidence
    ? confidence
    : `${confidencePercent}%`;

  // 通过类别名称哈希生成唯一颜色
  const surfaceColor = getColorByHash(class_name ?? "");

  const groupGroupConfig: KonvaGroupConfig = {
    vueComponentName: "v-group",
    konvaGraphConfig: {
      graphType: "target",
      x: left,
      y: top,
      width,
      height,
    },
    graphsConfigItems: [
      {
        vueComponentName: "v-rect",
        konvaGraphConfig: {
          stroke: surfaceColor,
          strokeWidth: 2,
          cornerRadius: 4,
          // shadowBlur: 4,
          // shadowOpacity: 0.5,
          // opacity: 0.1 + confidence * 0.9,
        },
      },
      {
        vueComponentName: "v-label",
        konvaGraphConfig: { x: width / 2 },
        graphsConfigItems: [
          {
            vueComponentName: "v-tag",
            konvaGraphConfig: {
              fill: surfaceColor,
              cornerRadius: 2,
              // shadowBlur: 4,
              // shadowOpacity: 0.5,
              // opacity: 0.1 + confidence * 0.9,
            },
          },
          {
            vueComponentName: "v-text",
            konvaGraphConfig: {
              text: `${class_name}: ${confidenceTip}`,
              fill: "rgb(255, 255, 255)",
              padding: 4,
              fontFamily: "Arial",
            },
          },
        ],
      },
    ],
  };

  return groupGroupConfig;
};
/**
 * 获取 Konva 骨骼组列表的返回结果类型
 */
export interface getKonvaListOfSkeletonGroupResult {
  konvaList: KonvaGroupConfig[];
  poseActionConfig?: KonvaGroupConfig;
}
/**
 * 获取 Konva 骨骼组列表
 *
 * @export
 * @param {MmC3d_InferenceResult} socketData
 * @param {("pose" | "hand" | "unConvertConfidence")} from
 * @return {*}  {getKonvaListOfSkeletonGroupResult}
 */
export function getKonvaListOfSkeletonGroup(
  socketData: MmC3d_InferenceResult,
  from: "pose" | "hand" | "target",
  unConvertConfidence?: boolean
): getKonvaListOfSkeletonGroupResult {
  const { action, results } = socketData;
  const result: getKonvaListOfSkeletonGroupResult = {
    konvaList: [],
    poseActionConfig: undefined,
  };

  results?.forEach((res) => {
    switch (from) {
      // case "pose":
      // 	result.konvaList.push(poseResultHandler(res));
      // 	break;
      // case "hand":
      // 	result.konvaList.push(handResultHandler(res));
      // 	break;
      case "target":
        result.konvaList.push(targetResultHandler(res, unConvertConfidence));
        break;
      default:
        break;
    }
  });

  // if (action && from === "pose") {
  // 	result.poseActionConfig = actionResultHandler(action);
  // }
  return result;
}
export function toZebraLineConfig(
  options: Record<string, any>
): any {
  return {
    rawPoints: options.points,
    points: options.points,
    stroke: options.stroke || "green", // 根据y坐标切换颜色
    strokeWidth: 3,
    lineCap: "square",
    name: options.class_name || options.class_id || "roi-line",
    closed: true, // 将线闭合形成多边形
    dash: options.dash || [],
  };
}

/** 返回roi斑马线配置 */
export function getRoiLineConfig(results: T_RoiSingleOfApi[]): KonvaGroupConfig {
  const groupGroupConfig: KonvaGroupConfig = {
    vueComponentName: "v-group",
    konvaGraphConfig: {
      id: uuidv4(),
      graphType: "roi",
    },
    graphsConfigItems: [],
  };

  results?.forEach((roi, i) => {
    const lineConfig: GraphsConfigItem = {
      vueComponentName: "v-line",
      konvaGraphConfig: toZebraLineConfig({
        points: roi.points.flat(),
        class_id: "line-roi" + i,
        stroke: "yellow",
      }),
    };

    const lineConfigDash: GraphsConfigItem = {
      vueComponentName: "v-line",
      konvaGraphConfig: toZebraLineConfig({
        points: roi.points.flat(),
        class_id: "line-roi-copy" + i,
        dash: [15, 14],
        stroke: "#000",
      }),
    };

    groupGroupConfig.graphsConfigItems?.push(lineConfig, lineConfigDash);
  });

  return groupGroupConfig;
}

export function uuidv4() {
	return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
		const r = crypto.getRandomValues(new Uint8Array(1))[0] & 15;
		const v = c === "x" ? r : (r & 0x3) | 0x8;
		return v.toString(16);
	});
}
